<?php
/**
 * Copyright 2011  SURFfoundation
 * 
 * This file is part of ESCAPE.
 * 
 * ESCAPE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * ESCAPE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with ESCAPE.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * For more information:
 * http://escapesurf.wordpress.com/
 * http://www.surffoundation.nl/
 * 
 * Contact: d.vierkant@utwente.nl
 * 
 * @license http://www.gnu.org/licenses/gpl.html GNU GPLv3
 */
?>
<?php
/**
 * Meta mapper
 * 
 * Maps RDF using mapping rules and filter statements not supported by the target RDF type.
 */
class escape_metamapper_MetaMapper
{
	/**
	 * MetaMapper config.
	 * @var array
	 */
	protected $config = null;
	
	/**
	 * Repository schema instance.
	 * @var escape_rdf_RepositorySchemaDocument
	 */
	public $repositorySchema = null;
	
	/**
	 * Create a new MetaMapper instance.
	 * @param array $config MetaMapper config array
	 * @param escape_rdf_RepositorySchemaDocument $repositorySchema repository schema
	 */
	public function __construct($config, $repositorySchema)
	{
		$this->config = $config;
		$this->repositorySchema = $repositorySchema;
	}
	
	/**
	 * Map an RDF type using the mappings defined in the configuration.
	 * If multiple RDF types are provided, it will pick the first RDF type that (in order):
	 * - is an RDF type which is a subclass of the ESCAPE root class
	 * or
	 * - is an RDF type which has a subclass which is a subclass of the ESCAPE root class
	 * A null will be returned if none of the (mapped) RDF types match the above mentioned conditions.  
	 * @param array $sourceRdfTypes RDF types
	 * @return string mapped RDF type or null if no valid RDF type was found 
	 */
	public function mapRdfType($sourceRdfTypes)
	{
		$typeMappings =& $this->config['type_mappings'];
		
		$targetRdfType = null;

		// loop over all source RDF types
		$mappedSourceRdfTypes = array();
		foreach($sourceRdfTypes as $sourceRdfType)
		{
			$mappedSourceRdfType = $sourceRdfType;
			
			// find candidate mappings
			$candidateMappings = array();
			foreach($typeMappings as $mappingIdx => $mapping)
			{
				// if the source type pattern is set, check if the source type pattern matches any of the expanded source types 
				$typePattern = '{' . $mapping['source_type'] . '}';
				
				if(preg_match($typePattern, $sourceRdfType))
				{
					// add the mapping index / priority to the candidates list
					$candidateMappings[$mappingIdx] = $mapping['priority'];
					
					break;
				}
			}
//			print_r($candidateMappings);
			
			// check if any mapping candidates are found 
			if(count($candidateMappings) > 0)
			{
				// use the candidate with the highest priority (= lowest priority number)
				asort($candidateMappings);
				$mapping = $typeMappings[key($candidateMappings)];

				// map the predicate
				$mappedSourceRdfType = $mapping['target_type'];
			}
//			print $sourceRdfType . ' -> ' . $mappedSourceRdfType . "\n";
			
			$mappedSourceRdfTypes[] = $mappedSourceRdfType;
		}
		
		
		// check if any of the RDF types is a selectable ESCAPE RDF type 
		foreach($mappedSourceRdfTypes as $sourceRdfType)
		{
			$escapeRootClassUri = $this->repositorySchema->getEscapeRootClassUri($sourceRdfType);
			if($escapeRootClassUri !== null)
			{
				$targetRdfType = $sourceRdfType;
				break;
			}
		}
		
		// check if a sub type of any of the RDF types is a selectable ESCAPE RDF type 
		if($targetRdfType === null)
		{
			foreach($mappedSourceRdfTypes as $sourceRdfType)
			{
				$subClassUris = $this->repositorySchema->getSubClassUriList($sourceRdfType);
				foreach($subClassUris as $subClassUri)
				{
					if($this->repositorySchema->getEscapeRootClassUri($subClassUri) !== null)
					{
						$targetRdfType = $sourceRdfType;
						break;
					}
				}
			}
		}
		
//		print_r($targetRdfType);
		
		return $targetRdfType;
	}
	
	/**
	 * Map a RDF resource using the mappings defined in the configuration.
	 * @param simplerdf_RdfResource $rdfResource RDF resource
	 * @param string $targetRdfType target RDF type
	 */
	protected function mapResource($rdfResource, $targetRdfType)
	{
		$predicateMappings =& $this->config['predicate_mappings'];
		
		$sourceRdfTypes = $rdfResource->getRdfTypes();
		
		// expand the source types with parent types from the schema
		$sourceRdfTypesExpanded = $sourceRdfTypes;
		foreach($sourceRdfTypes as $sourceRdfType)
		{
			$sourceRdfTypesExpanded = array_merge($sourceRdfTypesExpanded, $this->repositorySchema->getParentClassUriList($sourceRdfType));
		}

		// expand the target type with parent types from the schema
		$targetRdfTypesExpanded = $this->repositorySchema->getParentClassUriList($targetRdfType);
		$targetRdfTypesExpanded[] = $targetRdfType;
		
		// build a list of all allowed properties for the target RDF type specified
		$allowedClassPropertyList = $this->repositorySchema->getAllowedClassPropertyListWithDetails($targetRdfType, true);
		$allowedProperties = array();
		foreach($allowedClassPropertyList as $propertyUri => $value)
		{
			$allowedProperties[] = $propertyUri;
			// include all sub properties
			$allowedProperties = array_merge($allowedProperties, $this->repositorySchema->getAssignableSubPropertyUriList($propertyUri));
		}
		$allowedProperties = array_unique($allowedProperties);
		
		// create the target RDF resource
		$targetRdfResource = simplerdf_RdfResource::createRdfResource($rdfResource->getSubject());
		
		$targetRdfResource->setRdfType($targetRdfType);

		// map / copy all allowed properties
		$predicates = $rdfResource->getPredicates();
		foreach($predicates as $predicate)
		{
			$targetPredicate = $predicate;
			
			//TODO: multi pass mapping?
			
			// find candidate mappings
			$candidateMappings = array();
			foreach($predicateMappings as $mappingIdx => $mapping)
			{
				// source predicate must match
				if($mapping['source_predicate'] === $predicate)
				{
					// if the source type pattern is set, check if the source type pattern matches any of the expanded source types 
					if(isset($mapping['source_type']))
					{
						$typePattern = '{' . $mapping['source_type'] . '}';
						
						$typeMatch = false;
						foreach($sourceRdfTypesExpanded as $rdfType)
						{
							if(preg_match($typePattern, $rdfType))
							{
								$typeMatch = true;
								break;
							}
						}
						
						if(!$typeMatch)
						{
							continue;
						}
					}
					
					// if the target type pattern is set, check if the target type pattern matches any of the expanded target types 
					if(isset($mapping['target_type']))
					{
						$typePattern = '{' . $mapping['target_type'] . '}';
						
						$typeMatch = false;
						foreach($targetRdfTypesExpanded as $rdfType)
						{
							if(preg_match($typePattern, $rdfType))
							{
								$typeMatch = true;
								break;
							}
						}
						
						if(!$typeMatch)
						{
							continue;
						}
					}
					
					// add the mapping index / priority to the candidates list
					$candidateMappings[$mappingIdx] = $mapping['priority'];
				}
			}
//			print_r($candidateMappings);
			
			// check if any mapping candidates are found 
			if(count($candidateMappings) > 0)
			{
				// use the candidate with the highest priority (= lowest priority number)
				asort($candidateMappings);
				$mapping = $predicateMappings[key($candidateMappings)];

				// map the predicate
				$targetPredicate = $mapping['target_predicate'];
			}
//			print $predicate . ' -> ' . $targetPredicate . "\n";
			
			// check if the target predicate is allowed for the target type
			if(array_search($targetPredicate, $allowedProperties) !== false)
			{
				// copy the statements
				$objects = $rdfResource->getObjects($predicate);
				$targetRdfResource->addStatement($targetPredicate, $objects);
			}
		}
		
		return $targetRdfResource;
	}
	
	/**
	 * Map metadata to match the target RDF type. Only the subject and 1st degree related objects are mapped.
	 * @param simplerdf_RdfDocument $rdfDocument RDF document containing the metadata
	 * @param string $subjectUri URI of the subject
	 * @param string $targetRdfType target RDF type
	 * @return escape_metamapper_Result mapped metadata
	 */
	public function mapMetadata($rdfDocument, $subjectUri, $targetRdfType)
	{
		// create an empty target RDF document
		$mappedRdfDocument = simplerdf_RdfDocument::createRdfDocument();
		
		// check if the source document has any RDF resources
		if($rdfDocument->getResourceCount() > 0)
		{
			// fetch the subject RDF resource
			$subjectRdfResource = $rdfDocument->getResourceBySubject($subjectUri);
			if($subjectRdfResource === null){
				// the subject URI specified does does not exist in the RDF document
				
				// try fetching the URI of the aggregation object
				$subjectUri = $this->getAggregationObjectUri($rdfDocument);
				if($subjectUri !== null)
				{
					$subjectRdfResource = $rdfDocument->getResourceBySubject($subjectUri);
				}
				else
				{
					// pick the first RDF resource instead (not much else we can do)
					$subjectRdfResource = $rdfDocument->getResource(0);
					$subjectUri = $subjectRdfResource->getSubject();
				}
			}
			
			// map the predicates in the subject RDF resource
			$mappedSubjectRdfResource = $this->mapResource($subjectRdfResource, $targetRdfType);
			
			// add the mapped subject RDF resource to the target RDF document
			$mappedRdfDocument->addResource($mappedSubjectRdfResource);
			
			// find all first degree relations for the subject
			$predicates = $mappedSubjectRdfResource->getPredicates();
			foreach($predicates as $predicate)
			{
				$objects = $mappedSubjectRdfResource->getObjects($predicate);
				foreach($objects as $object)
				{
					// check if the related object exists in the source document
					$relatedRdfResource = $rdfDocument->getResourceBySubject($object);
					if($relatedRdfResource !== null)
					{
						// the related object exists in the source document
						// try to map the RDF type
						$relatedSourceRdfTypes = $relatedRdfResource->getRdfTypes();
						$relatedTargetRdfType = $this->mapRdfType($relatedSourceRdfTypes);
						
						if($relatedTargetRdfType !== null)
						{
							// RDF type mapped successfully
							
							// create a copy of the related RDF resource containing only non-relational properties
							$cleanedRelatedRdfResource = simplerdf_RdfResource::createRdfResource($relatedRdfResource->getSubject());
							$relatedRdfResourcePredicates = $relatedRdfResource->getPredicates();
							foreach($relatedRdfResourcePredicates as $relatedRdfResourcePredicate)
							{
								$relatedRdfResourceObjects = $relatedRdfResource->getObjects($relatedRdfResourcePredicate);
								foreach($relatedRdfResourceObjects as $relatedRdfResourceObject)
								{
									if($rdfDocument->getResourceBySubject($relatedRdfResourceObject) === null)
									{
										$cleanedRelatedRdfResource->addStatement($relatedRdfResourcePredicate, $relatedRdfResourceObject);
									}
								}
							}
							
							// map the predicates of the related RDF resource
							$mappedRelatedRdfResource = $this->mapResource($cleanedRelatedRdfResource, $relatedTargetRdfType);
							
							// add related RDF resource to the target document
							$mappedRdfDocument->addResource($mappedRelatedRdfResource);
						}
						else
						{
							// RDF type mapping of related resource failed, remove the relation as well
							$mappedSubjectRdfResource->removeStatement($predicate, $object);
						}
					}
				}
			}
		}
		
		$result = new escape_metamapper_Result();
		$result->setRdfDocument($mappedRdfDocument);
		$result->setSubjectUri($subjectUri);
		
		return $result;
	}

	/**
	 * Get the URI of the aggregation object.
	 * @param simplerdf_RdfDocument $rdfDocument Resource map document
	 * @return string aggregation URI
	 */
	protected function getAggregationObjectUri($rdfDocument)
	{
		$aggregationObjectUri = null;
		
		$rdfResources = $rdfDocument->getResources();
		foreach($rdfResources as $rdfResource)
		{
			/* @var $rdfResource simplerdf_RdfResource */
			if($rdfResource->hasRdfType('http://www.openarchives.org/ore/terms/Aggregation'))
			{
				$aggregationObjectUri = $rdfResource->getSubject();
				break;
			}
		}
		
		return $aggregationObjectUri;
	}
}
